﻿using System.CodeDom;
using System.Linq;
using Roslyn.Compilers.CSharp;

namespace InterfaceWeaver.CustomTool
{
    public static class CodeTypeDeclarationExtensions
    {
        /// <summary>
        /// Ensures the constructor declaration.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns></returns>
        public static CodeConstructor EnsureConstructorDeclaration(this CodeTypeDeclaration type)
        {
            var constructor = type.Members.OfType<CodeConstructor>().FirstOrDefault(member => member.Parameters.Count == 0);

            if(constructor == null)
            {
                constructor = new CodeConstructor
                    {
                        Attributes = MemberAttributes.Public
                    };
                type.Members.Add(constructor);
            }

            return constructor;
        }

        /// <summary>
        /// Ensures the initialize commands method declaration.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="callInConstructor">if set to <c>true</c> [call in constructor].</param>
        /// <returns></returns>
        public static CodeMemberMethod EnsureInitializeCommandsMethodDeclaration(this CodeTypeDeclaration type, bool callInConstructor = true)
        {
            var initializeCommandsMethod = type.Members
                .OfType<CodeMemberMethod>()
                .FirstOrDefault(method => 
                    method.Name == "InitializeCommands" 
                    && method.Parameters.Count == 0);

            if(initializeCommandsMethod == null)
            {
                initializeCommandsMethod = new CodeMemberMethod
                    {
                        Name = "InitializeCommands",
                        Attributes = MemberAttributes.Private
                    };
                initializeCommandsMethod.Comment(
                    summary: "Initializes the command mappings",
                    remarks: callInConstructor ? null : "The method should be called manualy in partial implementation.");
                type.Members.Add(initializeCommandsMethod);

                // Method call in constructor
                if (callInConstructor)
                {
                    EnsureConstructorDeclaration(type).Statements.Add(
                        new CodeMethodInvokeExpression(
                            new CodeMethodReferenceExpression(
                                new CodeThisReferenceExpression(),
                                initializeCommandsMethod.Name)));
                }
            }

            return initializeCommandsMethod;
        }

        /// <summary>
        /// Ensures the initialize component method declaration.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="callInConstructor">if set to <c>true</c> [call in constructor].</param>
        /// <returns></returns>
        public static CodeMemberMethod EnsureInitializeComponentMethodDeclaration(this CodeTypeDeclaration type, bool callInConstructor = true)
        {
            var initializeCommandsMethod = type.Members
                .OfType<CodeMemberMethod>()
                .FirstOrDefault(method =>
                    method.Name == "InitializeComponent"
                    && method.Parameters.Count == 0);

            if (initializeCommandsMethod == null)
            {
                initializeCommandsMethod = new CodeMemberMethod
                {
                    Name = "InitializeComponent",
                    Attributes = MemberAttributes.Private
                };
                initializeCommandsMethod.Comment(
                    summary: "Initializes the component",
                    remarks: callInConstructor ? null : "The method should be called manualy in partial implementation.");
                type.Members.Add(initializeCommandsMethod);

                // Method call in constructor
                if (callInConstructor)
                {
                    var callStatment = new CodeMethodInvokeExpression(
                        new CodeMethodReferenceExpression(
                            new CodeThisReferenceExpression(),
                            initializeCommandsMethod.Name));
                    if (EnsureConstructorDeclaration(type).Statements.Count == 0)
                    {
                        EnsureConstructorDeclaration(type).Statements.Add(callStatment);
                    }
                    else
                    {
                        EnsureConstructorDeclaration(type).Statements.Insert(0, new CodeExpressionStatement(callStatment));
                    }
                }
            }

            return initializeCommandsMethod;
        }

        /// <summary>
        /// Ensures the field instance declaration.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="typeToInstanciate">The type to instanciate.</param>
        /// <param name="constructorExpressions">The constructor expressions.</param>
        /// <returns></returns>
        public static CodeMemberField EnsureFieldInstanceDeclaration(
            this CodeTypeDeclaration type,
            TypeSyntax typeToInstanciate,
            params CodeExpression[] constructorExpressions)
        {
            return type.EnsureFieldInstanceDeclaration(typeToInstanciate, null, constructorExpressions);
        }

        /// <summary>
        /// Ensures the field instance declaration.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="typeToInstanciate">The type to instanciate.</param>
        /// <param name="instanciationSpecifications">The instanciation specifications.</param>
        /// <param name="constructorExpressions">The constructor expressions.</param>
        /// <returns></returns>
        public static CodeMemberField EnsureFieldInstanceDeclaration(
            this CodeTypeDeclaration type, 
            TypeSyntax typeToInstanciate,
            string instanciationSpecifications,
            params CodeExpression[] constructorExpressions)
        {
            var typeName = typeToInstanciate.ToFullString().Trim();
            var instanceName = string.Format("{0}Instance", typeName.ToCamelCase());
            var instance = type.Members.OfType<CodeMemberField>().FirstOrDefault(field => field.Name == instanceName);
           
            if(instance == null)
            {
                instance = new CodeMemberField(typeName, instanceName);
                var constructor = type.EnsureInitializeComponentMethodDeclaration();

                var initializationStatment = new CodeAssignStatement(
                    new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), instanceName),
                    new CodeObjectCreateExpression(typeName, constructorExpressions));
                if (constructor.Statements.Count == 0)
                {
                    if (!string.IsNullOrWhiteSpace(instanciationSpecifications))
                    {
                        constructor.Statements.Add(new CodeSnippetStatement(string.Empty));
                        constructor.Statements.Add(new CodeCommentStatement(instanciationSpecifications));
                    }
                    constructor.Statements.Add(initializationStatment);
                }
                else
                {
                    if (!string.IsNullOrWhiteSpace(instanciationSpecifications))
                    {
                        constructor.Statements.Insert(0, new CodeSnippetStatement(string.Empty));
                        constructor.Statements.Insert(0, new CodeCommentStatement(instanciationSpecifications));
                    }
                    constructor.Statements.Insert(0, initializationStatment);
                }

                type.Members.Add(instance);
            }

            return instance;
        }

        /// <summary>
        /// Ensures the generic property changes declaration.
        /// </summary>
        /// <param name="type">The type.</param>
        public static void EnsureGenericPropertyChangesDeclaration(this CodeTypeDeclaration type)
        {
            const string changingText = "\t\tpartial void OnPropertyChangingCallBack(string propertyName);";
            const string changedText = "\t\tpartial void OnPropertyChangedCallBack(string propertyName);";

            if (type.Members.OfType<CodeSnippetTypeMember>().All(p => p.Text != changingText))
            {
                var valueChangingHook = new CodeSnippetTypeMember(changingText);
                type.Members.Add(valueChangingHook);
            }

            if (type.Members.OfType<CodeSnippetTypeMember>().All(p => p.Text != changedText))
            {
                var valueChangedHook = new CodeSnippetTypeMember(changedText);
                type.Members.Add(valueChangedHook);
            }
        }

        /// <summary>
        /// Determines whether the specified type is inheriting.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="type">The type.</param>
        /// <returns>
        ///   <c>true</c> if the specified type is inheriting; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsInheriting<T>(this CodeTypeDeclaration type)
        {
            var baseType = typeof (T);
            return type.BaseTypes.OfType<CodeTypeReference>().Any(@base => @base.BaseType == baseType.Name || @base.BaseType == baseType.FullName);
        }
    }
}
